package com.example.scribe.backend.handlers;

import com.example.scribe.SyntaxNode;
import com.example.scribe.backend.ASTUtils;
import com.example.scribe.backend.BytecodeOps;
import com.example.scribe.backend.ExpressionEmitter;
import com.example.scribe.backend.ExpressionNodeHandler;
import com.example.scribe.backend.LocalVariableAllocator;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackManipulation.Compound;

import java.util.ArrayList;
import java.util.List;

/**
 * Handler for function call expressions.
 */
public final class CallExpressionHandler implements ExpressionNodeHandler {
    private final ExpressionEmitter exprEmitter;

    public CallExpressionHandler(ExpressionEmitter exprEmitter) {
        this.exprEmitter = exprEmitter;
    }

    @Override
    public boolean canHandle(String nodeType) {
        return "Call".equals(nodeType);
    }

    @Override
    public StackManipulation handle(SyntaxNode node, String generatedClassName, LocalVariableAllocator locals) {
        if (node == null) {
            return StackManipulation.Trivial.INSTANCE;
        }

        String callee = null;
        List<SyntaxNode> args = new ArrayList<>();

        // Extract callee and arguments
        for (SyntaxNode c : node.children()) {
            if ("primary".equals(c.type())) {
                for (SyntaxNode p : c.children()) {
                    if (ASTUtils.isIdentifier(p)) {
                        String text = p.text();
                        if (text != null && !text.isEmpty()) {
                            callee = text;
                            break; // Found identifier, stop searching
                        }
                    }
                    // Also check if p itself is an identifier node (might be nested)
                    if ("identifier".equals(p.type()) || "Identifier".equals(p.type())) {
                        String text = p.text();
                        if (text != null && !text.isEmpty()) {
                            callee = text;
                            break;
                        }
                    }
                    // If identifier node has children, check Terminal:IDENT
                    if (p.children().size() > 0) {
                        for (SyntaxNode child : p.children()) {
                            if (child.type().startsWith("Terminal:IDENT") || "Terminal:IDENT".equals(child.type())) {
                                String text = child.text();
                                if (text != null && !text.isEmpty()) {
                                    callee = text;
                                    break;
                                }
                            }
                        }
                        if (callee != null) break;
                    }
                }
            }
            if ("Args".equals(c.type())) {
                args.addAll(c.children());
            }
        }

        if (callee == null) {
            // Not a valid call form
            return StackManipulation.Trivial.INSTANCE;
        }

        // Handle zero-argument calls
        if (args.isEmpty()) {
            // Check if it's a local variable instead
            if (locals.hasName(callee)) {
                return BytecodeOps.loadInt(locals.getIndexByName(callee));
            }
            // Function call with no args
            String owner = generatedClassName.replace('.', '/');
            return BytecodeOps.invokeStaticInternal(owner, callee, "()I");
        }

        // Handle multi-argument calls
        List<StackManipulation> pushes = new ArrayList<>();
        for (SyntaxNode argExpr : args) {
            // Get the inner expression node
            SyntaxNode inner = null;
            for (SyntaxNode ic : argExpr.children()) {
                inner = ic;
                break;
            }
            if (inner == null) {
                inner = argExpr;
            }
            pushes.add(exprEmitter.emit(inner, generatedClassName, locals));
        }

        String owner = generatedClassName.replace('.', '/');
        String desc = BytecodeOps.descriptorForIntArgs(args.size());
        StackManipulation[] parts = new StackManipulation[pushes.size() + 1];
        for (int i = 0; i < pushes.size(); i++) {
            parts[i] = pushes.get(i);
        }
        parts[pushes.size()] = BytecodeOps.invokeStaticInternal(owner, callee, desc);
        return new Compound(parts);
    }
}

